Skip to main content

Infrastructure diagrams

Mermaid diagrams of the ABAIR infrastructure, in two groups:

  1. Operations — the infrastructure layout, request routing, and how changes are made.
  2. Security — how the infrastructure maps onto the NIS2 CyberFundamentals BASIC control set.

Each diagram below is also a standalone .mmd file in this directory with the same content, so it can be pasted directly into mermaid.live or a Mermaid VS Code extension for editing. PNG exports (white background) of every diagram are in this folder too.

Conventions used across every diagram​

  • Laid out top to bottom on a fixed column grid. Every diagram is one centred column that flows down the page, narrow enough (about 750px) to render at full size in the docs column. Where boxes sit side by side they form two or three aligned columns, and every box uses one of three fixed widths (full width = two columns + gap = three columns + gaps), so box edges line up vertically through the whole diagram, including inside nested boxes.
  • Server/host/service names render about 1.35x larger than their detail text, so a box reads "webserver" in bold with 10.0.0.14 · nginx, sole public TLS ingress underneath in normal size. Applied everywhere a node names an actual machine or running service (hosts, VMs, Amergin/SecOps components, vendors). Not applied to process/workflow steps (diagram 2) or backup-job labels, where there's no single "name" to emphasize.
  • Boxes in the same row share the same height, so a diagram reads as a grid of consistent cards rather than boxes sized by however much text happened to be in each one.
  • hetzner-main is drawn as a single dark-gold box (the Proxmox VE hypervisor), and it contains everything that runs on it — proxmox-host itself, every VM (webserver, api-gateway, services-user, services-staff, synthesis-legacy, docker-build), and the legacy scealai exception — since they're all one physical machine. Only genuinely separate hardware/providers (Amergin, secops-host, vaultwarden-host) get their own top-level box.
  • Consistent color coding, reused across all 11 diagrams:
    • indigo = internal host/VM
    • red, heavier border = fully isolated host (secops-host, vaultwarden-host)
    • gray dashed = legacy / outside current IaC (scealai)
    • blue = external vendor/third-party dependency
    • purple = data store (Vaultwarden, backups, NAS)
    • green = verified-working / compliant control
    • amber = accepted risk / known gap
    • sky blue = on-prem (TCD campus) host, used only in diagram 10
  • Arrows only join consecutive steps down the column (or steps within one column of a grid). Where a relationship would need an arrow cutting across the layout (for example, the webserver reverse-proxying secops-host and vaultwarden-host), it is written in the relevant box or in a dashed note box instead. An arrow that points at a whole box (such as hetzner-main → Amergin) names its actual source or target in its label.
  • The NIS2-mapped diagrams (04–08) each carry a small Legend box explaining their color key, since their color keys carry compliance meaning.
  • Security diagrams are annotated with the relevant CyberFundamentals BASIC control IDs (e.g. PR.IR-01.1) — cross-reference against the PDF's table of contents for the full requirement text.

Scope boundaries (deliberate):

  • The legacy services VM (10.0.0.2, decommissioned) and the puca recording Pi are not shown in any diagram.
  • The on-premises TCD servers (phoneticsrv3, fionn, banba, brigid, setanta, NAS) appear only in diagram 10. In particular, phoneticsrv3 is never shown alongside services-user in any other diagram, even though services-user does call it (legacy proxy_pass) — keeping the cloud estate and the on-prem estate visually and conceptually separate.
  • Diagram 0 (global overview) shows only Supabase as a third-party dependency, to keep it readable — the full vendor breakdown (Hetzner, Cloudflare, HuggingFace, GitHub, Bitwarden.com, Pyannote) is diagram 8's job.

0. Global overview​

Everything in one diagram: ingress, all hosts/zones, backups, and the Amergin → Gitea edge (Amergin pulls code + models from Gitea, models over LFS; nothing is copied from an admin's machine). Dense by design — a reference diagram for orientation; the diagrams that follow break each part out in more detail.

Operations​

1. VM & service inventory​

One box per host, grouped by what runs where. The "where does X live" reference.

2. Deploy / change workflow​

The actual admin path: unlock secrets from Vaultwarden, then follow one of three columns — make provision (Terraform, rare/deliberate), make setup-*/update-* (Ansible, routine) or make deploy-* (new app image) — all ending on the target VM or host. This is the "how do I make a change" reference.

3. Request routing​

Cloudflare → nginx on the webserver VM → backend VM:port. Each backend VM is one box listing the domains (and ports) nginx proxies to it, all inside the hetzner-main box (matching diagram 0); Amergin and the secops/vaultwarden domains sit outside it. recognition.abair.ie is gone — batch-recognition on Amergin is reached only through api-gateway's internal-only routes now, not a direct public domain.

9. Generic app request pattern (Supabase + api-gateway)​

Confirmed against the live app repos (2026-09-22): most services-user/services-staff Next.js apps (backoffice, fotheidil, cul, translations-editor, litreoir, and others) share the same scaffold — the browser talks to Supabase directly for auth and most DB reads/writes (anon key, RLS-enforced), a narrower server-side path uses a service-role key for object storage only, and backend calls that need billing/quota attribution go through api-gateway carrying the Supabase session token as a Bearer credential. Some apps (backoffice, cul, geabaire-web-editor) call their own dedicated service subdomain directly instead of the shared gateway (shown as the "Exception" box at the bottom). The three columns are the three kinds of browser request, each headed by how it authenticates.

10. On-premises / TCD campus servers — ID.AM-01, ID.AM-02​

The physical hardware outside the Hetzner cloud estate entirely: phoneticsrv3 (on-prem web server for ABAIR services, still called from abair.ie/old.abair.ie via legacy proxy_pass), four GPU servers used for model training (fionn, banba, brigid, setanta), and NAS network-attached storage. None of these are in Terraform/Ansible, and none are enrolled in Wazuh or Alloy monitoring (see diagram 6) — an asset-inventory/monitoring gap. Kept deliberately separate from every other diagram — see "Scope boundaries" above. The dashed connection (trained models → Gitea LFS) and the "not centrally monitored" callout are inferred from the rest of the architecture rather than explicitly confirmed.

Security — NIS2 CyberFundamentals BASIC​

4. Network segmentation & trust zones — PR.IR-01.1, PR.IR-01.2​

Trust zones from public internet down to fully isolated hosts. hetzner-main is one physical hypervisor internally segmented into the hypervisor/firewall layer, the DMZ, and the private-VLAN app tier — all nested inside a single outer box, since it's genuinely one machine — with the firewall/boundary at each hop named explicitly.

5. Identity, access & secrets management — GV.RR-04, PR.AA-01, PR.AA-03, PR.AA-05​

SSH bastion + key-based admin access, Vaultwarden as the central secrets store with collection-based least-privilege access, and a per-service authentication/2FA status callout — including the honest gaps (Wazuh/Uptime Kuma shared admin accounts, no MFA — an accepted risk with a stated compensating control).

6. Monitoring, detection & response — DE.CM-01, DE.CM-03, DE.AE-03, RS.MA-01, RS.CO-02​

The monitored hosts are grouped into two columns by what they ship: all 10 run a Wazuh agent (solid arrow to the Wazuh manager), but only the 6 in the left "Wazuh agent + Grafana Alloy" column — webserver, api-gateway, services-user, services-staff, synthesis-legacy, amergin — also ship metrics/logs to Loki/Prometheus for Grafana (dashed arrow). That gap between the two — docker-build, proxmox-host, secops-host, and vaultwarden-host get a Wazuh agent but don't ship metrics/logs to Grafana — is a real coverage difference. Also shows the one known coverage gap (scealai, deliberately outside IaC) and the fact that a formal incident-response runbook doesn't exist yet.

7. Backup & recovery — PR.DS-11, RC.RP-01​

The two parallel backup systems (Proxmox vzdump for VMs, Borg for the two isolated hosts) into a shared Hetzner Storage Box, plus two real findings: a live test showing Borg's --append-only flag didn't actually stop deletion (real protection is the Storage Box's own snapshots), and a ~3-week silent backup gap that was caught by manual audit, not monitoring.

8. Third-party / supply-chain dependencies — GV.OC-03, ID.AM-02​

Vendor concentration on Hetzner, Cloudflare as the DNS/edge layer, Supabase as the hosted BaaS underpinning most Next.js apps (see diagram 9 for how), and an active, in-progress migration off GitHub (Amergin apps done 2026-10, the services-user/staff web apps still to go), and already-completed migrations off HuggingFace (ABAIR's own models, 2026-10) and Bitwarden.com, onto self-hosted equivalents the team fully controls.